/** @author temujin Aug 31, 2011 */
package paim.gh.controller.eventos;


import java.awt.Component;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.logging.Level;

import javax.swing.tree.TreeNode;

import org.hibernate.Session;

import paim.wingchun.app.Reflections;
import paim.wingchun.app.WC_App;
import paim.wingchun.app.WC_Log;
import paim.wingchun.controller.Selecionar;
import paim.wingchun.model.modelos.Model;
import paim.wingchun.model.pojos.Erro;
import paim.wingchun.model.pojos.Pojo;
import paim.wingchun.view.AbstractView;
import paim.wingchun.view.Dialogs;

import paim.gh.app.GHApp;
import paim.gh.app.Gradiador;
import paim.gh.model.DAO.AulaDAO;
import paim.gh.model.DAO.ProfessorDAO;
import paim.gh.model.modelos.BaldeModel;
import paim.gh.model.modelos.DoscenteModel;
import paim.gh.model.modelos.ProfessorModel;
import paim.gh.model.modelos.TurmaModel;
import paim.gh.model.pojos.Aula;
import paim.gh.model.pojos.Balde;
import paim.gh.model.pojos.Disciplina;
import paim.gh.model.pojos.Doscente;
import paim.gh.model.pojos.Professor;
import paim.gh.model.pojos.Turma;
import paim.gh.view.paineis.arvore.Arvore;
import paim.gh.view.paineis.arvore.ArvoreListener;
import paim.gh.view.paineis.arvore.ArvoreModel;
import paim.gh.view.paineis.arvore.ArvoreNode;

import static paim.wingchun.app.Reflections.getField;
import static paim.wingchun.app.Reflections.getValorField;

/** @author temujin Aug 31, 2011 */
public class ArvoreEvent extends Observable implements ArvoreListener {

    // FIXME o menu deveria ser sensivel ao contexto conforme objetos disponiveis

    WeakReference<Arvore> arvore;

    public ArvoreEvent() {}

    public ArvoreEvent(Arvore arvore) {
        this.arvore = new WeakReference<Arvore>(arvore);

    }

    private ArvoreModel getModel() {
        return (ArvoreModel) arvore.get().getModel();
    }

    private Session getSessao() {
        return GHApp.getSessao();
    }

    private ArvoreNode getNode() {
        // FIXME ava.lang.NullPointerException quando menu tree esta vazio, ...
        return (ArvoreNode) arvore.get().getLastSelectedPathComponent();
    }

    protected void changeSomething(Object[] arg) {
        /* Notify observers of change */
        setChanged();
        notifyObservers(arg);
    }

    protected void changeSomething(Arvore.Eventos eventos) {
        /* Notify observers of change */
        setChanged();
        notifyObservers(new Object[] { eventos, getNode() });
    }

    @Override
    public void add() {
        final ArvoreNode nodo = getNode();
        changeSomething(Arvore.Eventos.ADD);

        Class<? extends Pojo> classe = nodo.getClasse();

        final Balde balde = Gradiador.getInstance().getBalde();

        try {
            Field field = getField(Balde.class, classe);
            @SuppressWarnings("unchecked")
            final List<Pojo> listaObjetos = (List<Pojo>) getValorField(balde, field);

            @SuppressWarnings({ "rawtypes", "unchecked", "unused" })
            final Selecionar<?> contro = new Selecionar(null, balde, arvore.get(), classe) {

                @Override
                protected List getPojosIniciais() throws Exception {
                    /* os pojos populados inicialmente devem ser os do banco menos os ja utilizados */
                    List<Pojo> objetos = getModelo().complementary(getSessao(), listaObjetos);
                    return objetos;
                }

                /* ignora o super */
                @Override
                protected void apos_setObjeto(Object objeto) {}

                @Override
                protected void apos_confirmar(Object objeto, List erros, AbstractView.Estado estado) {
                    getVisao().fechar();

                    List<Pojo> selecao = (List<Pojo>) getSelecao();
                    if ( selecao.isEmpty() )
                        return;

                    addObjetos(selecao, nodo);
                }
            };
        }
        catch ( Exception e ) {
            Dialogs.erro(e.toString(), "Add");
            WC_Log.getInstancia().getlogger().log(Level.SEVERE, e.getMessage(), e);
        }

    }

    @Override
    public void addAll() {
        final ArvoreNode nodo = getNode();
        changeSomething(Arvore.Eventos.ADDALL);

        Class<? extends Pojo> classe = nodo.getClasse();

        /* se for o balde entao */
        if ( Balde.class.isAssignableFrom(classe) )
            for ( int index = 0; index < getModel().getChildCount(nodo); index++ ) {
                ArvoreNode nodoChild = (ArvoreNode) getModel().getChild(nodo, index);
                addAllObjetos(nodoChild);
            }
        else
            addAllObjetos(nodo);
    }

    @Override
    public void show() {
        // TODO usar icone em arvore mostrando que esta mostrado
        // TODO Auto-generated method stub

    }

    @Override
    public void showAll() {
        // TODO Auto-generated method stub

    }

    @Override
    public void hide() {
        // TODO usar icone em arvore mostrando que esta ocultado
        // TODO Auto-generated method stub

    }

    @Override
    public void hideAll() {
        // TODO Auto-generated method stub

    }

    @Override
    public void remove() {
        final ArvoreNode nodo = getNode();
        changeSomething(Arvore.Eventos.REMOVE);
        removeObjeto(nodo);
    }

    @Override
    public void removeAll() {
        final ArvoreNode nodo = getNode();
        changeSomething(Arvore.Eventos.REMOVEALL);

        Class<? extends Pojo> classe = nodo.getClasse();
        /* se for o balde entao */
        if ( Balde.class.isAssignableFrom(classe) )
            for ( int index = 0; index < getModel().getChildCount(nodo); index++ ) {
                ArvoreNode nodoChild = (ArvoreNode) getModel().getChild(nodo, index);
                removeAllObjetos(nodoChild);
            }
        else
            removeAllObjetos(nodo);
    }

    @Override
    public void addTurma() {
        final ArvoreNode nodo = getNode();
        changeSomething(Arvore.Eventos.ADDTURMA);
        addObjeto(nodo);
    }

    @Override
    public void removeTurma() {
        final ArvoreNode nodo = getNode();
        changeSomething(Arvore.Eventos.REMOVETURMA);
        removeObjeto(nodo);

    }

    @Override
    public void selecao() {
        changeSomething(Arvore.Eventos.SELECAO);
    }

    private void removeObjeto(ArvoreNode nodo) {
        List<Erro> errosExclusao = new ArrayList<>();
        final TreeNode nodoPai = nodo.getParent();
        Class<? extends Pojo> classe = nodo.getClasse();
        Pojo pojo = nodo.getObjeto();

        try {
            if ( Aula.class.isAssignableFrom(classe) || Professor.class.isAssignableFrom(classe) ) {
                throw new Exception("nodos invalido para remocao");
            }
            else if ( Turma.class.isAssignableFrom(classe) ) {
                Turma turma = (Turma) pojo;
                Aula aula = turma.getAula();

                TurmaModel.getInstance().removeTurma(getSessao(), aula, turma);
                /* se ainda tem 3 ou mais turmas */
                if ( nodo.getSiblingCount() > 2 ) {
                    /* remove o nodo da arvore */
                    nodo.removeFromParent();
                }
                /* se so tem duas turmas entao tem que acabar com todas folhas */
                if ( nodo.getSiblingCount() == 2 ) {
                    ((ArvoreNode) nodoPai).removeAllChildren();
                }
                if ( nodo.isLeaf() ) {

                }
                /* recarrega o nodo ancenstral */
                getModel().reload(nodoPai);

                errosExclusao = Gradiador.getInstance().saveOrUpdate();

            }
            else if ( Doscente.class.isAssignableFrom(classe) ) {
                /* preciso encontrar a professor que corresponde a doscente passada */
                Doscente doscente = (Doscente) pojo;
                Professor professor = ProfessorDAO.getInstance().getProfessor(getSessao(),
                                Gradiador.getInstance().getGrade(), doscente);

                ArvoreNode nodoProfessor = getModel().findNode(professor);

                if ( ProfessorModel.getInstance().disengageAndRemove(getSessao(), professor) ) {
                    /* remove nodo */
                    nodoProfessor.removeFromParent();
                    getModel().reload(getModel().getNodeProfessores());

                    if ( DoscenteModel.getInstance().disengageAndRemove(getSessao(), doscente) ) {
                        /* remove o nodo da arvore */
                        nodo.removeFromParent();
                        /* recarrega o nodo ancenstral */
                        getModel().reload(nodoPai);
                    }
                }
            }
            else if ( Disciplina.class.isAssignableFrom(classe) ) {
                /* preciso encontrar a aula que corresponde a disciplina passada */
                Aula aula = AulaDAO.getInstance().getAula(getSessao(), Gradiador.getInstance().getGrade(),
                                (Disciplina) pojo);
                ArvoreNode nodoAula = getModel().findNode(aula);
                /* remover objeto */
                Gradiador.getInstance().getGrade().getAula().remove(aula);

                /* agora trata de remover a disciplina */
                Gradiador.getInstance().getBalde().getDisciplinas().remove(pojo);

                /* remove nodo */
                nodoAula.removeFromParent();
                getModel().reload(getModel().getNodeAula());

                /* remove o nodo da arvore */
                nodo.removeFromParent();
                /* recarrega o nodo ancenstral */
                getModel().reload(nodoPai);
                getModel().reload(getModel().getNodeAula());
            }
            /** eh um qualquer do balde */
            else {
                /* pega o field do atributo lista do balde */
                Field field = getField(Balde.class, classe);
                /* busca a lista de objetos */
                final List<Pojo> listaObjetos = (List) getValorField(Gradiador.getInstance().getBalde(), field);
                /* remove o selecionado */
                listaObjetos.remove(pojo);
                /* persiste o balde com sessao */
                errosExclusao = BaldeModel.getInstance().saveOrUpdate(getSessao(), Gradiador.getInstance().getBalde());
                /* remove o nodo da arvore */
                nodo.removeFromParent();
                /* recarrega o nodo ancenstral */
                getModel().reload(nodoPai);
            }

        }
        catch ( Exception e ) {
            // TODO melhorar saida
            Dialogs.erro(e.toString(), "Remover");
            WC_Log.getInstancia().getlogger().log(Level.SEVERE, e.getMessage(), e);
        }
        finally {
            if ( !errosExclusao.isEmpty() )
                Dialogs.showMessageErros((Component) WC_App.getInstancia().getVisaoApp(), errosExclusao);
        }
    }

    /** @author temujin Jun 12, 2013
     * @param nodo
     *            void */
    private void removeAllObjetos(ArvoreNode nodo) {
        Class<? extends Pojo> classe = nodo.getClasse();
        List<Erro> errosPersistencia = new ArrayList<>();

        try {
            errosPersistencia = removeAllObjetos(nodo, classe);
        }
        catch ( Exception e ) {
            Dialogs.erro(e.toString(), "Add");
            WC_Log.getInstancia().getlogger().log(Level.SEVERE, e.getMessage(), e);
        }
        finally {
            if ( !errosPersistencia.isEmpty() )
                // TODO
                Dialogs.showMessageErros((Component) WC_App.getInstancia().getVisaoApp(), errosPersistencia);
        }
    }

    private List<Erro> removeAllObjetos(ArvoreNode nodo, Class<? extends Pojo> classe) throws Exception {
        Balde balde = Gradiador.getInstance().getBalde();

        /* busca o field da lista */
        Field field = getField(Balde.class, classe);

        /* get lista */
        List<Pojo> listaObjetos = (List<Pojo>) getValorField(balde, field);

        /* limpa lista */
        listaObjetos.clear();

        /* persiste o balde com sessao do facade */
        List<Erro> errosPersistencia = BaldeModel.getInstance().saveOrUpdate(getSessao(), balde);

        /* remove os nodos na arvore */
        nodo.removeAllChildren();

        /* recarrega o nodo ancenstral */
        getModel().reload(nodo);

        return errosPersistencia;
    }

    private void addObjeto(ArvoreNode nodo) {
        List<Erro> errosPersistencia = new ArrayList<>();
        final TreeNode nodoPai = nodo.getParent();
        Class<? extends Pojo> classe = nodo.getClasse();
        Pojo pojo = nodo.getObjeto();
        try {
            if ( Aula.class.isAssignableFrom(classe) ) {
                Aula aula = (Aula) pojo;
                /* adicionar uma turma */
                Turma nova_turma = TurmaModel.getInstance().addTurma(getSessao(), aula);
                /* deixar evidente a existencia da primeira turma (unica) se estiver adicionando a segunda turma */
                if ( nodo.isLeaf() ) {
                    /* adiciono esse objeto aula desse nodo como uma folha (novo nodo) */
                    nodo.add(aula.primeiraTurma());
                }
                /* adiciono a turma clonada como uma folha tambem */
                nodo.add(nova_turma);
                getModel().reload(nodo);
                // FIXME errosPersistencia = saveOrUpdate();
            }
        }
        catch ( Exception e ) {
            e.printStackTrace();
            Dialogs.erro(e.toString(), "Add Turma");
            WC_Log.getInstancia().getlogger().log(Level.SEVERE, e.getMessage(), e);
        }
        finally {
            if ( !errosPersistencia.isEmpty() )
                // TODO
                Dialogs.showMessageErros((Component) WC_App.getInstancia().getVisaoApp(), errosPersistencia);
        }
    }

    /** retorna a lista de objetos adicionados
     *
     * @author paim 01/11/2011
     * @param classe
     * @return List<POJO> objetos realemente adicionados */
    private List<Pojo> addAllObjetos(ArvoreNode nodo) {
        Class<? extends Pojo> classe = nodo.getClasse();
        List<Erro> errosPersistencia = new ArrayList<>();
        List<Pojo> adicionados = new ArrayList<>();

        try {
            Field field = getField(Balde.class, classe);
            @SuppressWarnings("unchecked")
            List<Pojo> pojos = (List<Pojo>) getValorField(Gradiador.getInstance().getBalde(), field);

            /* busco o modelo desse */
            Model<?> modelo = Reflections.getModel(classe);
            /* os pojos populados inicialmente devem ser os do banco menos os ja utilizados */
            List<Pojo> complement = modelo.complementary(getSessao(), Pojo.ids(pojos));

            addObjetos(complement, nodo);

        }
        catch ( Exception e ) {
            Dialogs.erro(e.toString(), "AddAll");
            WC_Log.getInstancia().getlogger().log(Level.SEVERE, e.getMessage(), e);
        }
        finally {
            if ( !errosPersistencia.isEmpty() )
                // TODO
                Dialogs.showMessageErros((Component) WC_App.getInstancia().getVisaoApp(), errosPersistencia);
        }

        return adicionados;
    }

    private List<Pojo> addObjetos(List<Pojo> objetos, ArvoreNode nodo) {
        Class<? extends Pojo> classe = nodo.getClasse();
        List<Pojo> adicionados = new ArrayList<>();
        List<Erro> errosPersistencia = new ArrayList<>();
        /* TODO nao ta legal, deveria certificar de que consgui salvar e depois criar o nodo , acho que nesse caso, vou
         * recarregar a grade e redesenhar tudo, mas aviso que nao pode ser caregado */
        try {
            if ( Doscente.class.isAssignableFrom(classe) ) {
                for ( Pojo pojo : objetos ) {
                    /* adiciono um doscente */
                    Gradiador.getInstance().getBalde().getDoscentes().add((Doscente) pojo);

                    /* adiciona um novo nodo doscente */
                    nodo.add(pojo);

                    /* adiciono um professor */
                    Professor professor = new Professor(Gradiador.getInstance().getGrade(), (Doscente) pojo);
                    Gradiador.getInstance().getGrade().getProfessor().add(professor);

                    /* adiciona um novo nodo professor */
                    getModel().getNodeProfessores().add(professor);

                    adicionados.add(pojo);
                }
                getModel().reload(nodo);
                getModel().reload(getModel().getNodeProfessores());

                errosPersistencia = Gradiador.getInstance().saveOrUpdate();
            }
            else if ( Disciplina.class.isAssignableFrom(classe) ) {
                for ( Pojo pojo : objetos ) {
                    /* adiciono uma disciplina */
                    Gradiador.getInstance().getBalde().getDisciplinas().add((Disciplina) pojo);

                    /* adiciona um novo nodo disciplina */
                    nodo.add(pojo);

                    /* adiciono um aula */
                    Aula aula = new Aula(Gradiador.getInstance().getGrade(), (Disciplina) pojo);
                    Gradiador.getInstance().getGrade().getAula().add(aula);

                    /* e naturalmente tem que adicionar uma turma */
                    TurmaModel.getInstance().addTurma(getSessao(), aula);
                    /* adiciona um novo nodo aula */

                    getModel().getNodeAula().add(pojo);
                    adicionados.add(pojo);
                }
                getModel().reload(nodo);
                getModel().reload(getModel().getNodeAula());

                errosPersistencia = Gradiador.getInstance().saveOrUpdate();
            }
            else { /* qualquer outro do balde */
                Field field = getField(Balde.class, classe);
                List<Pojo> listaAtual = (List<Pojo>) getValorField(Gradiador.getInstance().getBalde(), field);
                /* adiciono a lista original */
                listaAtual.addAll(objetos);

                /* adiciona os nodos na arvore */
                nodo.addAll(objetos);
                /* recarrega o nodo ancenstral */
                getModel().reload(nodo);

                /* persiste o balde com sessao */
                errosPersistencia = BaldeModel.getInstance().saveOrUpdate(getSessao(),
                                Gradiador.getInstance().getBalde());
                Gradiador.getInstance().getErros().addAll(errosPersistencia);
            }
        }
        catch ( Exception e ) {
            Dialogs.erro(e.toString(), "Add");
            WC_Log.getInstancia().getlogger().log(Level.SEVERE, e.getMessage(), e);
        }
        finally {
            if ( !errosPersistencia.isEmpty() )
                // TODO
                Dialogs.showMessageErros((Component) WC_App.getInstancia().getVisaoApp(), errosPersistencia);
        }

        return adicionados;
    }

}
